package net.lulihu.pangolin.headless.client.component;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import net.lulihu.pangolin.headless.client.config.StartupConfig;
import net.lulihu.pangolin.headless.client.handler.HeartbeatCheckHandler;
import net.lulihu.pangolin.headless.client.handler.ProxyClientMessageHandler;
import net.lulihu.pangolin.headless.client.handler.RealServerDataHandler;
import net.lulihu.pangolin.headless.client.kit.CmdChannelKit;
import net.lulihu.pangolin.headless.client.listener.ChannelConnectListener;
import net.lulihu.pangolin.headless.common.container.Application;
import net.lulihu.pangolin.headless.common.container.annotation.Autowired;
import net.lulihu.pangolin.headless.common.container.annotation.Component;
import net.lulihu.pangolin.headless.common.container.templet.ApplicationLifeCycle;
import net.lulihu.pangolin.headless.common.model.AuthAndPenetrate;
import net.lulihu.pangolin.headless.common.protocol.ProtocolMessage;
import net.lulihu.pangolin.headless.common.protocol.ProtocolMessageDecoder;
import net.lulihu.pangolin.headless.common.protocol.ProtocolMessageEncoder;
import net.lulihu.pangolin.headless.common.protocol.ProtocolMessageType;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 客户端网络组件
 */
@Slf4j
@Component
public class ClientNetworkComponent implements ApplicationLifeCycle {

    private EventLoopGroup workGroup;
    private Bootstrap realServerBootstrap;
    private Bootstrap proxyServerBootstrap;

    @Autowired
    private StartupConfig startupConfig;

    @Override
    public void containerLoadSuccessAfter() {
        this.workGroup = new NioEventLoopGroup();
        this.realServerBootstrap = realServerBootstrap();
        this.proxyServerBootstrap = proxyServerBootstrap();

        retryConnection();
    }

    /**
     * 重试连接
     */
    public void retryConnection() {
        try {
            retryConnection(3);
        } catch (InterruptedException e) {
            log.error("连接代理服务器线程被中断", e);
            // 中断则退出
            Application.stop();
        }
    }

    // 当前是否在重新连接
    private volatile boolean retryConnection = false;

    /**
     * 重试连接
     *
     * @param interval 重试间隔，单位秒
     * @throws InterruptedException 线程等待被中断
     */
    public void retryConnection(int interval) throws InterruptedException {
        if (retryConnection) return;
        try {
            retryConnection = true;
            while (true) {
                // 尝试连接
                boolean connection = connection();
                if (connection) break;

                Thread.sleep(interval * 1000);
            }
        } finally {
            retryConnection = false;
        }
    }

    /**
     * 发起连接
     *
     * @return true则连接成功反之则失败
     */
    public boolean connection() {
        log.info("尝试连接代理服务器...");

        CountDownLatch downLatch = new CountDownLatch(1);
        AtomicBoolean boo = new AtomicBoolean(false);

        // 获取启动配置
        final String host = startupConfig.getHost();
        final Integer port = startupConfig.getPort();

        // 连接代理服务器
        proxyServerConnect(host, port, new ChannelConnectListener() {
            @Override
            public void connectSuccess(Channel channel) {
                log.info("连接代理服务器成功...");
                boo.set(true);
                CmdChannelKit.setCmdChannel(channel);

                AuthAndPenetrate authAndPenetrate = new AuthAndPenetrate();
                authAndPenetrate.setSecret(startupConfig.getSecret());
                authAndPenetrate.setPenetrates(startupConfig.getPenetrates());

                ProtocolMessage message = new ProtocolMessage();
                message.setType(ProtocolMessageType.C_AUTH_AND_PENETRATE);
                message.setBodyObj(authAndPenetrate);
                channel.writeAndFlush(message);

                if (log.isDebugEnabled()) log.debug("发送认证并申请穿透消息");
            }

            @Override
            public void connectFail() {
                log.warn("代理服务器连接失败");
                boo.set(false);
            }

            @Override
            public void complete() {
                downLatch.countDown();
            }
        });

        try {
            downLatch.await();
        } catch (InterruptedException e) {
            log.error("等待连接时线程被中断", e);
        }

        return boo.get();
    }

    /**
     * 连接真实服务器的引导程序
     */
    private Bootstrap realServerBootstrap() {
        return new Bootstrap().group(workGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new RealServerDataHandler());
                    }
                });
    }

    /**
     * 连接代理服务器的引导程序
     */
    private Bootstrap proxyServerBootstrap() {
        Bootstrap proxyClientBootstrap = new Bootstrap();
        return proxyClientBootstrap.group(workGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new ProtocolMessageDecoder());
                        pipeline.addLast(new ProtocolMessageEncoder());
                        pipeline.addLast(new HeartbeatCheckHandler());
                        pipeline.addLast(new ProxyClientMessageHandler());
                    }
                });
    }


    /**
     * 连接到真实服务器
     *
     * @param host                   主机地址
     * @param port                   访问端口
     * @param channelConnectListener 通道连接事件
     */
    public void realServerConnect(String host, int port, final ChannelConnectListener channelConnectListener) {
        channelConnect(this.realServerBootstrap, host, port, channelConnectListener);
    }

    /**
     * 连接到代理服务器
     *
     * @param host                   主机地址
     * @param port                   访问端口
     * @param channelConnectListener 通道连接事件
     */
    public void proxyServerConnect(String host, int port, final ChannelConnectListener channelConnectListener) {
        channelConnect(this.proxyServerBootstrap, host, port, channelConnectListener);
    }

    private void channelConnect(Bootstrap bootstrap, String host, int port,
                                final ChannelConnectListener channelConnectListener) {
        ChannelFuture connect = bootstrap.connect(host, port);
        if (channelConnectListener != null) {
            connect.addListener((ChannelFutureListener) future -> {
                try {
                    if (future.isSuccess()) {
                        channelConnectListener.connectSuccess(future.channel());
                    } else {
                        channelConnectListener.connectFail();
                    }
                } finally {
                    channelConnectListener.complete();
                }
            });
        }
    }

    @Override
    public void applicationShowdown() {
        log.info("工作线程组关闭...");

        workGroup.shutdownGracefully();
    }
}
